"""
Unit Tests for Bias Correction Module

Tests all bias correction components including BiasCorrector, BiasPredictors, 
VarBC algorithms, and real-time bias correction application.
"""

using Test
using LinearAlgebra
using Statistics
using Dates

@testset "Bias Correction Tests" begin
    
    @testset "BiasCorrector Core Types Tests" begin
        # Test VarBC scheme configuration
        varbc_scheme = VarBCScheme(
            [1, 4, 7, 10],  # channels
            [:constant, :lapse_rate, :scan_angle],  # predictors
            adaptation_window=50,
            convergence_threshold=1e-6,
            regularization_weight=0.01
        )
        
        @test length(varbc_scheme.channels) == 4
        @test length(varbc_scheme.predictors) == 3
        @test varbc_scheme.adaptation_window == 50
        @test varbc_scheme.convergence_threshold ≈ 1e-6
        
        # Test bias configuration
        config = BiasConfiguration{Float64}(
            VarBCScheme,
            schemes=[varbc_scheme],
            diagnostics=true,
            parallel=true
        )
        
        @test config.scheme_type == VarBCScheme
        @test length(config.active_schemes) == 1
        @test config.output_diagnostics == true
        @test config.parallel_processing == true
        
        # Test bias state initialization
        state = BiasState{Float64}()
        @test state.current_iteration == 0
        @test isempty(state.convergence_history)
        @test state.is_converged == false
    end
    
    @testset "Bias Predictors Tests" begin
        # Create mock observation
        loc = GeographicLocation{Float64}(40.0, -75.0, 850.0)
        qm = QualityMetrics{Float64}(0.9, 0.95, 0.85, 0.8, 0.9)
        
        radiance_data = SatelliteRadianceData{Float64}(
            "AIRS", "Aqua", [7], [250.8], [0.4], 45.2, 35.0
        )
        
        obs = RadianceObservation{Float64}(
            RADIANCE, loc, DateTime(2024, 1, 15, 12, 0, 0),
            250.8, 0.4, qm, Dict{String, Any}(), [0, 0, 0, 0],
            7, radiance_data
        )
        
        # Test constant predictor
        const_pred = ConstantPredictor{Float64}(1.0)
        @test evaluate_predictor(const_pred, obs) ≈ 1.0
        
        # Test lapse rate predictor
        lapse_pred = LapseRatePredictor{Float64}(200.0, 1, 1.0)
        lapse_value = evaluate_predictor(lapse_pred, obs)
        @test lapse_value isa Float64
        @test isfinite(lapse_value)
        
        # Test latitude predictor
        lat_pred_cos = LatitudePredictor{Float64}(:cosine, 1.0)
        lat_value = evaluate_predictor(lat_pred_cos, obs)
        @test lat_value ≈ cos(deg2rad(40.0))
        
        lat_pred_sin = LatitudePredictor{Float64}(:sine, 1.0)
        sin_value = evaluate_predictor(lat_pred_sin, obs)
        @test sin_value ≈ sin(deg2rad(40.0))
        
        # Test scan angle predictor
        scan_pred = ScanAnglePredictor{Float64}(2, 58.0, 0.0)
        scan_value = evaluate_predictor(scan_pred, obs)
        @test scan_value isa Float64
        @test scan_value ≥ 0.0  # Should be non-negative for squared angle
        
        # Test radiance bias predictors
        rad_predictors = RadianceBiasPredictors{Float64}(:infrared)
        @test length(rad_predictors.predictors) > 0
        @test rad_predictors.instrument_type == :infrared
        
        # Test predictor evaluation for multiple predictors
        predictors = [const_pred, lapse_pred, lat_pred_cos, scan_pred]
        results = evaluate_all_predictors(obs, predictors)
        @test length(results) ≥ 4
        @test all(isfinite, results)
        
        # Test predictor names
        names = get_predictor_names(predictors)
        @test length(names) == length(predictors)
        @test "constant" in names
        @test "latitude_cosine" in names
    end
    
    @testset "VarBC Algorithm Tests" begin
        # Test VarBC configuration
        config = VarBCConfig{Float64}(
            max_iter=10,
            conv_thresh=1e-4,
            reg_strength=0.05
        )
        
        @test config.max_iterations == 10
        @test config.convergence_threshold ≈ 1e-4
        @test config.regularization_strength ≈ 0.05
        
        # Test VarBC state initialization
        state = initialize_varbc(
            config,
            [:airs_aqua],
            Dict(:airs_aqua => zeros(Float64, 5, 3))  # 5 channels, 3 predictors
        )
        
        @test haskey(state.coefficients, :airs_aqua)
        @test size(state.coefficients[:airs_aqua]) == (5, 3)
        @test haskey(state.coefficient_covariance, :airs_aqua)
        
        # Test VarBC minimizer
        minimizer = VarBCMinimizer{Float64}(config)
        @test minimizer.config == config
        @test minimizer.optimization_method == :conjugate_gradient
        
        # Test convergence checking
        state.convergence_history = [1.0, 0.5, 0.1, 1e-5, 1e-6]
        state.gradient_norm_history = [1.0, 0.5, 0.1, 1e-5, 1e-6]
        @test check_varbc_convergence(state, config) == true
        
        # Test validation
        validation_errors = validate_varbc_configuration(config)
        @test isempty(validation_errors)
        
        # Test invalid configuration
        bad_config = VarBCConfig{Float64}(max_iter=-1, conv_thresh=-1.0)
        bad_errors = validate_varbc_configuration(bad_config)
        @test !isempty(bad_errors)
    end
    
    @testset "Bias Application Tests" begin
        # Test bias application configuration
        app_config = BiasApplicationConfig{Float64}(
            coeff_file="test_coeffs.jld2",
            batch_size=500,
            parallel=true,
            gross_checks=true,
            innov_thresh=5.0
        )
        
        @test app_config.coefficient_file == "test_coeffs.jld2"
        @test app_config.batch_size == 500
        @test app_config.parallel_processing == true
        @test app_config.apply_gross_checks == true
        @test app_config.innovation_threshold ≈ 5.0
        
        # Test bias application state
        app_state = BiasApplicationState{Float64}()
        @test app_state.observations_processed == 0
        @test app_state.observations_corrected == 0
        @test app_state.is_operational == false
        @test app_state.average_processing_time ≈ 0.0
        
        # Test real-time bias corrector initialization
        corrector = RealTimeBiasCorrector{Float64}(app_config)
        @test corrector.config == app_config
        @test isa(corrector.state, BiasApplicationState{Float64})
        @test isa(corrector.processing_lock, ReentrantLock)
        
        # Test performance metrics
        perf_metrics = BiasPerformanceMetrics{Float64}(
            100.0,  # throughput
            [10.0, 25.0, 50.0, 90.0],  # latency percentiles
            0.85,   # memory efficiency
            75.0,   # CPU utilization
            Dict(:cache => 0.95),  # cache performance
            Dict(:timeout => 0.01),  # error rates
            Hour(1),
            now()
        )
        
        @test perf_metrics.throughput ≈ 100.0
        @test length(perf_metrics.latency_percentiles) == 4
        @test perf_metrics.memory_efficiency ≈ 0.85
        
        # Test quality assurance metrics
        qa_metrics = BiasQualityAssurance{Float64}(
            Dict(:airs => 0.05),  # coefficient stability
            Dict(:airs => 0.02),  # innovation consistency
            Dict(:airs => zeros(Float64, 3, 3)),  # spatial consistency
            Dict(:airs => [0.1, 0.05, 0.02]),  # temporal consistency
            Dict(:airs => [1, 5, 10]),  # outlier detection
            Dict(:airs => Dict(1 => :good, 7 => :suspect)),  # quality flags
            now(),
            Hour(6)
        )
        
        @test haskey(qa_metrics.coefficient_stability, :airs)
        @test qa_metrics.coefficient_stability[:airs] ≈ 0.05
        @test length(qa_metrics.temporal_consistency[:airs]) == 3
    end
    
    @testset "Bias Correction Integration Tests" begin
        # Test complete bias correction workflow
        
        # Create mock observations
        observations = []
        for i in 1:10
            loc = GeographicLocation{Float64}(40.0 + i, -75.0 + i, 850.0)
            qm = QualityMetrics{Float64}(0.8, 0.9, 0.7, 0.8, 0.85)
            
            radiance_data = SatelliteRadianceData{Float64}(
                "AIRS", "Aqua", [7], [250.0 + i], [0.4], 45.0, 35.0
            )
            
            obs = RadianceObservation{Float64}(
                RADIANCE, loc, DateTime(2024, 1, 15, 12, i, 0),
                250.0 + i, 0.4, qm, Dict{String, Any}(), [0, 0, 0, 0],
                7, radiance_data
            )
            push!(observations, obs)
        end
        
        # Test bias coefficient initialization
        config = BiasConfiguration{Float64}(VarBCScheme)
        state = initialize_bias_correction(
            config,
            [RADIANCE]
        )
        
        @test isa(state, BiasState{Float64})
        @test state.last_update isa DateTime
        
        # Test bias correction application (mock)
        corrected_obs = []
        for obs in observations
            # Mock bias correction
            bias_correction = 0.1 * randn()
            corrected_value = obs.value - bias_correction
            
            corrected = ObservationBias{Float64}(
                RADIANCE, obs.location, obs.time,
                obs.value, corrected_value, bias_correction,
                0.05, [:varbc]
            )
            push!(corrected_obs, corrected)
        end
        
        @test length(corrected_obs) == 10
        @test all(obs -> obs isa ObservationBias{Float64}, corrected_obs)
        
        # Test statistics computation
        bias_magnitudes = [abs(obs.bias_correction) for obs in corrected_obs]
        mean_bias = mean(bias_magnitudes)
        @test mean_bias ≥ 0.0
        @test isfinite(mean_bias)
    end
    
    @testset "Error Handling and Edge Cases" begin
        # Test empty observation set
        empty_obs = AbstractObservation{Float64}[]
        config = BiasConfiguration{Float64}(VarBCScheme)
        
        @test_nowarn initialize_bias_correction(config, ObservationType[])
        
        # Test single observation
        loc = GeographicLocation{Float64}(40.0, -75.0, 850.0)
        qm = QualityMetrics{Float64}(0.9, 0.9, 0.9, 0.9, 0.9)
        single_obs = [SurfaceObservation{Float64}(
            TEMPERATURE, loc, now(), 285.0, 0.5, qm,
            Dict{String, Any}(), [0, 0, 0, 0]
        )]
        
        @test length(single_obs) == 1
        
        # Test extreme bias values
        extreme_bias = 1000.0  # Very large bias
        @test isfinite(extreme_bias)
        
        # Test numerical stability
        tiny_error = 1e-10
        large_error = 1e10
        @test tiny_error > 0.0
        @test isfinite(large_error)
        
        # Test predictor edge cases
        const_pred = ConstantPredictor{Float64}(0.0)
        @test evaluate_predictor(const_pred, single_obs[1]) ≈ 0.0
        
        # Test boundary coordinates
        boundary_obs = SurfaceObservation{Float64}(
            TEMPERATURE, 
            GeographicLocation{Float64}(90.0, 180.0, 0.01),  # North pole, dateline
            now(), 250.0, 1.0, qm, Dict{String, Any}(), [0, 0, 0, 0]
        )
        
        lat_pred = LatitudePredictor{Float64}(:cosine, 1.0)
        boundary_value = evaluate_predictor(lat_pred, boundary_obs)
        @test boundary_value ≈ cos(deg2rad(90.0)) atol=1e-10  # Should be ~0
    end
end